/***********************************************************************
 * nScreen - An Advanced Console Application Framework for .NET
 * 
 * Copyright (C) 2007 by:
 *  Justin Long (dukk(a)dukk.org)
 *  Fabio Cavalcante (fabio(a)codesapien.com)
 *  Josh Perry (josh(a)6bit.com)
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Library General Public License as 
 * published by  the Free Software Foundation; either version 2 of the 
 * License or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Library General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this package (See COPYING.LIB); if not, write to the 
 * Free Software Foundation, Inc.
 * 59 Temple Place - Suite 330
 * Boston, MA 02111-1307, USA.
 ***********************************************************************/

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace nScreen
{
	public class ScreenApplication
	{
		private static bool _renderSuspended = false;

		private Screen _screen;
		private Thread _readKeyThread;
		private ManualResetEvent _runningResetEvent;

		public static event EventHandler<ScreenApplicationKeyPressEventArgs> KeyPress;

		private static ScreenApplication _instance;

		private ScreenApplication()
		{
			Console.CursorVisible = false;
		}

		public static bool RenderSuspended
		{
			get { return _renderSuspended; }
			private set { _renderSuspended = value; }
		}

		public Screen Screen
		{
			get { return _screen; }
			private set
			{
				_screen = value;

				this.Screen.Invalidate();
			}
		}

		private void initialize()
		{
			_runningResetEvent = new ManualResetEvent(false);

			_readKeyThread = new Thread(delegate()
			{
				while (true)
				{
					if (null == ScreenApplication.Instance)
					{
						break;
					}

					if (Console.KeyAvailable)
					{
						ConsoleKeyInfo consoleKeyInfo = Console.ReadKey(true);

						ScreenApplication.OnKeyPress(this, consoleKeyInfo);
					}

					if (!Console.KeyAvailable)
					{
						Thread.Sleep(50);
					}
				}
			});

			_readKeyThread.IsBackground = true;
			_readKeyThread.Start();

			_runningResetEvent.WaitOne();
		}

		internal static void OnKeyPress(object sender, ConsoleKeyInfo consoleKeyInfo)
		{
			EventHandler<ScreenApplicationKeyPressEventArgs> handler = ScreenApplication.KeyPress;
			ScreenApplicationKeyPressEventArgs e = new ScreenApplicationKeyPressEventArgs(consoleKeyInfo);

			if (null != handler)
			{
				handler(sender, e);
			}
		}

		internal static ScreenApplication Instance
		{
			get { return _instance; }
			private set { _instance = value; }
		}

		internal void ExitInternal()
		{
			ScreenApplication.Instance = null;

			Console.Clear();

			_runningResetEvent.Set();
		}

		public static void Run(Screen screen)
		{
			if (null != ScreenApplication.Instance)
			{
				throw new InvalidOperationException("Only one ConsoleApplication can be run at a time.");
			}

			ScreenApplication.Instance = new ScreenApplication();
			ScreenApplication.Instance.Screen = screen;
			ScreenApplication.Instance.Screen.DoInitialize();
			ScreenApplication.Instance.Screen.DoRender();
			ScreenApplication.Instance.initialize(); // Blocking call... starts waiting for key input
		}

		public static void SuspendRender()
		{
			ScreenApplication.RenderSuspended = true;
		}

		public static void ResumeRender()
		{
			ScreenApplication.RenderSuspended = false;
		}

		public static void TransitionScreen(Screen screen)
		{
			if (null == ScreenApplication.Instance)
			{
				throw new InvalidOperationException("No ConsoleApplication instance to exit from, you must call run before you can exit.");
			}

			ScreenApplication.Instance.Screen = screen;
			ScreenApplication.Instance.Screen.Invalidate();
		}

		public static void Exit()
		{
			if (null == ScreenApplication.Instance)
			{
				throw new InvalidOperationException("No ConsoleApplication instance to exit from, you must call run before you can exit.");
			}

			ScreenApplication.Instance.ExitInternal();
		}
	}
}